package avicit.bdp.dgs.qa.service;

import avicit.bdp.common.base.BaseService;
import avicit.bdp.common.service.dto.DataSourceDTO;
import avicit.bdp.common.service.service.DataSourceService;
import avicit.bdp.common.utils.BdpLogUtil;
import avicit.bdp.core.constant.Constants;
import avicit.bdp.dgs.qa.dao.JobDAO;
import avicit.bdp.dgs.qa.dao.RuleDAO;
import avicit.bdp.dgs.qa.dto.*;
import avicit.bdp.dgs.qa.utils.constant.QaConstant;
import avicit.bdp.dgs.qa.utils.coverter.SqlTemplateConverter;
import avicit.bdp.dgs.qa.utils.enums.*;
import avicit.bdp.dgs.qa.utils.tools.FileAdapterUtils;
import avicit.platform6.api.system.ConvertColumnClient;
import avicit.platform6.api.system.impl.SystemConstant;
import avicit.platform6.commons.utils.BusinessUtil;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.commons.utils.PojoUtil;
import avicit.platform6.core.context.ThreadContextHelper;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.properties.PlatformConstant;
import avicit.platform6.core.rest.msg.QueryRespBean;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.visitor.SchemaStatVisitor;
import com.alibaba.druid.stat.TableStat;
import com.alibaba.fastjson2.JSON;
import com.github.pagehelper.Page;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @金航数码科技有限责任公司
 * @作者：developer
 * @邮箱：developer@avic-digital.com
 * @创建时间： 2023-03-30
 * @类说明：RuleService
 * @修改记录：
 * @注意事项：
 * @主要功能：质量规则Service
 */
@Service
public class RuleService extends BaseService<RuleDAO, RuleDTO> {
    private static final Logger logger = LoggerFactory.getLogger(RuleService.class);

    @Autowired
    private JobDAO jobDAO;

    @Autowired
    private DataSourceService commDsService;

    @Autowired
    private QaDataSourceService qaDsService;

    @Autowired
    private AlarmService alarmService;

    @Autowired
    private RuleTemplateService ruleTemplateService;

    @Autowired
    private SqlTemplateConverter sqlTemplateConverter;

    @Autowired
    private ConvertColumnClient convertColumnClient;

    @Autowired
    private RuleVariableService ruleVariableService;

    /**
     * 新增质量规则
     *
     * @param ruleDTO
     * @return
     */
    @Transactional
    public String addRule(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            throw new BusinessException("参数为空");
        }
        if (ruleDTO.getMeasureType() == null) {
            throw new BusinessException("校验类型为空");
        } else {
            if (MeasureType.getDescByCode(ruleDTO.getMeasureType()) == null) {
                throw new BusinessException("监控类型不合法,当前值：" + ruleDTO.getMeasureType() + ",取值范围[0,6]");
            }
        }

        ruleDTO.setId(ComUtil.getId());
        MeasureType measureType = MeasureType.getTypeByCode(ruleDTO.getMeasureType());
        switch (measureType) {
            case SINGLE_SOURCE:
            case MULTI_SOURCE:
                addStructuredDataRule(ruleDTO);
                break;
            case MULTI_DATABASE:
                addMultiDbRule(ruleDTO);
                break;
            case FILE:
                addFileRule(ruleDTO);
                break;
            default:
                throw new BusinessException("暂不支持" + measureType.getDesc());
        }

        return ruleDTO.getId();
    }

    /**
     * 文件校验时，自动根据文件生成规则
     *
     * @param jobDTO
     * @return
     */
    public List<RuleDTO> autoCreateRule(JobDTO jobDTO) {
        List<RuleDTO> ruleList = autoGenerateRule(jobDTO);
        if (CollectionUtils.isNotEmpty(ruleList)) {
            for (RuleDTO rule : ruleList) {
                this.insert(rule);
            }
        }

        return ruleList;
    }

    /**
     * 根据文件自动生成规则
     *
     * @param jobDTO
     * @return
     */
    public List<RuleDTO> autoGenerateRule(JobDTO jobDTO) {
        List<FtdpFileDTO> fileList = getFileList(jobDTO);
        List<RuleTemplateDTO> templateList = this.ruleTemplateService.getFileTemplateList();

        Map<String, List<FtdpFileDTO>> map = FileAdapterUtils.detectTemplate(jobDTO.getFileType(), jobDTO.getDataProp(), templateList, fileList);
        if (map == null || map.size() <= 0) {
            logger.error("根据内置模板自动生成规则失败.");
            return new ArrayList<>();
        }

        List<RuleDTO> ruleList = new ArrayList<>();
        Map<String, RuleTemplateDTO> templateMap = templateList.stream().collect(Collectors.toMap(RuleTemplateDTO::getId, item -> item));
        for (Map.Entry<String, List<FtdpFileDTO>> entry : map.entrySet()) {
            String templateId = entry.getKey();
            if (StringUtils.isEmpty(templateId)) {
                continue;
            }

            RuleTemplateDTO currTemplate = templateMap.get(templateId);
            if (currTemplate == null) {
                continue;
            }

            RuleDTO rule = new RuleDTO();
            rule.setId(ComUtil.getId());
            rule.setJobId(jobDTO.getId());
            rule.setName(currTemplate.getName());
            rule.setTemplateType(TemplateType.BUILT_IN.getCode());
            rule.setTemplateId(templateId);
            rule.setTemplateName(currTemplate.getName());
            rule.setMeasureType(MeasureType.FILE.getCode());
            rule.setMeasureTypeName(MeasureType.FILE.getDesc());
            rule.setWeight(QaConstant.DEFAULT_WEIGHT);

            ruleList.add(rule);
        }

        return ruleList;
    }

    /**
     * 删除/批量删除质量规则，多个id使用英文逗号隔开
     *
     * @param id
     * @return
     */
    @Transactional
    public int deleteRule(String id) {
        if (StringUtils.isBlank(id)) {
            return 0;
        }

        this.mapper.deleteRuleByCond(null, Arrays.asList(id.trim().split(Constants.COMMA)));
        this.qaDsService.deleteDsByCond(id, null);
        this.alarmService.deleteAlarmByCond(id, null);
        this.ruleVariableService.deleteVariable(id);

        return 0;
    }

    /**
     * 根据jobId删除规则列表
     *
     * @param jobId
     * @return
     */
    @Transactional
    public int deleteRuleByJobId(String jobId) {
        if (StringUtils.isBlank(jobId)) {
            return 0;
        }

        List<String> ruleIdList = this.mapper.selectRuleIdListByJobId(jobId);
        for (String ruleId : ruleIdList) {
            this.qaDsService.deleteDsByCond(ruleId, null);
            this.alarmService.deleteAlarmByCond(ruleId, null);
            this.ruleVariableService.deleteVariable(ruleId);
        }

        return this.mapper.deleteRuleByCond(jobId, null);
    }

    /**
     * 修改质量规则
     *
     * @param ruleDTO
     * @return
     */
    @Transactional
    public boolean updateRule(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            throw new BusinessException("参数为空");
        }
        if (ruleDTO.getMeasureType() == null) {
            throw new BusinessException("校验类型为空");
        } else {
            if (MeasureType.getDescByCode(ruleDTO.getMeasureType()) == null) {
                throw new BusinessException("监控类型不合法,当前值：" + ruleDTO.getMeasureType() + ",取值范围[0,6]");
            }
        }

        MeasureType measureType = MeasureType.getTypeByCode(ruleDTO.getMeasureType());
        switch (measureType) {
            case SINGLE_SOURCE:
            case MULTI_SOURCE:
                updateStructuredDataRule(ruleDTO);
                break;
            case MULTI_DATABASE:
                updateMultiDbRule(ruleDTO);
                break;
            case FILE:
                updateFileRule2Db(ruleDTO);
                break;
            default:
                throw new BusinessException("暂不支持" + measureType.getDesc());
        }

        return true;
    }

    /**
     * 查询质量规则详情
     *
     * @param id 质量规则ID
     * @return
     */
    @Transactional
    public RuleDTO getRuleDetail(String id) {
        if (StringUtils.isBlank(id)) {
            logger.warn("规则ID为空");
            return null;
        }

        RuleDTO dto = this.mapper.getRuleDetail(id);
        if (dto == null) {
            logger.warn("根据id=" + id + "未找到规则信息");
            return null;
        }

        // 转换
        valueConvert(Arrays.asList(dto));

        return dto;
    }

    /**
     * 分页查询规则列表
     *
     * @param jobId
     * @param keyWords
     * @param measureType
     * @param templateType
     * @return
     */
    @Transactional
    public QueryRespBean<RuleDTO> queryRule(String jobId, String keyWords, Integer measureType, Integer templateType,
                                            Integer pageNo, Integer pageSize) {
        QueryRespBean<RuleDTO> queryRespBean = new QueryRespBean<>();

        com.github.pagehelper.PageHelper.startPage(pageNo, pageSize);
        Page<RuleDTO> queryRet = this.mapper.queryRuleByCond(jobId, keyWords, measureType, templateType);
        queryRespBean.setResult(queryRet);

        valueConvert(queryRet.getResult());

        return queryRespBean;
    }

    /**
     * 判读规则名称是否重复
     *
     * @param jobId  作业ID
     * @param ruleId 规则ID，规则编辑时修改名称，需要传入ruleId;新建场景不需要传入
     * @param name   规则名称
     * @return
     */
    @Transactional
    public boolean isSameName(String jobId, String ruleId, String name) {
        long count = this.mapper.selectNameCount(jobId, ruleId, name);
        if (count > 0) {
            return true;
        }

        return false;
    }

    /**
     * 批量设置权重
     *
     * @param rules
     * @return
     */
    @Transactional
    public boolean setRuleWeight(List<RuleDTO> rules) {
        if (CollectionUtils.isEmpty(rules)) {
            logger.warn("设置权重时，规则列表为空");
            return true;
        }

        List<RuleDTO> finalRules = Lists.newArrayList();
        for (RuleDTO dto : rules) {
            if (dto == null) {
                continue;
            }
            if (StringUtils.isBlank(dto.getId())) {
                continue;
            }
            if (dto.getWeight() == null) {
                continue;
            }

            finalRules.add(dto);
        }

        // 批量更新
        this.mapper.batchUpdateRules(finalRules);

        return true;
    }

    /**
     * 预览质量规则SQL
     *
     * @param ruleDTO
     * @return
     */
    @Transactional
    public String previewSql(RuleDTO ruleDTO) {
        setRuleSql(ruleDTO);

        return ruleDTO.getAbnormalSql();
    }

    /**
     * 不分页-查询指定作业下的规则列表
     *
     * @param jobId
     * @return
     */
    public List<RuleDTO> getRuleListByJobId(String jobId) {
        if (StringUtils.isEmpty(jobId)) {
            return new ArrayList<>();
        }

        List<RuleDTO> ruleList = this.mapper.getRuleByCond(jobId, new ArrayList<>());
        valueConvert(ruleList);

        return ruleList;
    }

    /**
     * 获取质量规则数量
     *
     * @return
     */
    @Transactional
    public Long countRule(String startTime, String endTime, List<String> wordSecretList) {
        return this.mapper.countRule(startTime, endTime, wordSecretList);
    }

    /**
     * 验证SQL语法合法性
     * 注意：
     * 1、新增规则
     * （1）内置模板，先生成abnormalSql，后校验；
     * （2）自定义模板，先生成abnormalSql，后校验；
     * （3）自定义SQL，先生成DataSourceList，再生成abnormalSql，后校验；
     * 2、编辑规则（简化处理，跟新增流程保持一致）
     * （1）内置模板，先生成abnormalSql，后校验；
     * （2）自定义模板，先生成abnormalSql，后校验；
     * （3）自定义SQL，先生成DataSourceList，再生成abnormalSql，后校验；
     *
     * @param ruleDTO
     * @return
     */
    public boolean validSql(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            logger.warn("入参为空，SQL语法校验失败");
            return false;
        }

        // 自定义SQL规则需要设置数据源列表
        setDefaultDatasourceList(ruleDTO);

        // 设置异常SQL、表行数统计SQL
        setRuleSql(ruleDTO);

        // 获取数据源信息
        return this.qaDsService.checkGrammar(getDsIdFromJob(ruleDTO.getJobId()), ruleDTO.getAbnormalSql());
    }

    /**
     * 新增规则时合法性校验
     *
     * @param ruleDTO
     */
    private void checkStructuredDataAddValid(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            throw new BusinessException("入参为空.");
        }
        if (StringUtils.isBlank(ruleDTO.getName())) {
            throw new BusinessException("规则名称为空.");
        }
        if (StringUtils.isBlank(ruleDTO.getJobId())) {
            throw new BusinessException("作业ID为空.");
        }
        if (ruleDTO.getMeasureType() == null) {
            throw new BusinessException("监控类型为空.");
        } else {
            if (MeasureType.getDescByCode(ruleDTO.getMeasureType()) == null) {
                throw new BusinessException("监控类型不合法,当前值：" + ruleDTO.getMeasureType() + ",取值范围[0,6]");
            }
        }
        if (ruleDTO.getTemplateType() == null) {
            throw new BusinessException("模板类型为空.");
        } else {
            if (MeasureType.getDescByCode(ruleDTO.getTemplateType()) == null) {
                throw new BusinessException("模板类型不合法,当前值：" + ruleDTO.getTemplateType() + ",取值范围[0,2]");
            }
        }
        if (TemplateType.CUSTOM_SQL != TemplateType.getTypeByCode(ruleDTO.getTemplateType())) {
            if (StringUtils.isBlank(ruleDTO.getTemplateId())) {
                throw new BusinessException("模板ID为空.");
            }
            if (CollectionUtils.isEmpty(ruleDTO.getDataSourceList())) {
                throw new BusinessException("数据源配置为空.");
            }
        }
    }

    /**
     * 更新作业时合法性检查
     *
     * @param ruleDTO
     */
    private void checkStructuredDataUpdateValid(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            throw new BusinessException("入参为空.");
        }
        if (StringUtils.isBlank(ruleDTO.getId())) {
            throw new BusinessException("作业主键ID为空.");
        }
    }

    /**
     * 新增规则时合法性校验
     *
     * @param ruleDTO
     */
    private void checkMultiDbAddValid(RuleDTO ruleDTO) {
        // TODO:待实现
    }

    /**
     * 更新作业时合法性检查
     *
     * @param ruleDTO
     */
    private void checkMultiDbUpdateValid(RuleDTO ruleDTO) {
        // TODO:待实现
    }

    /**
     * 新增规则，支持单表校验、跨表校验
     *
     * @param ruleDTO
     * @return
     */
    public void addStructuredDataRule(RuleDTO ruleDTO) {
        checkStructuredDataAddValid(ruleDTO);

        saveSturcturedData2Db(ruleDTO);
    }

    /**
     * 新增跨库校验规则
     *
     * @param ruleDTO
     * @return
     */
    public void addMultiDbRule(RuleDTO ruleDTO) {
        checkMultiDbAddValid(ruleDTO);

        saveMultiDb2Db(ruleDTO);
    }

    /**
     * 新增文件校验规则
     *
     * @param ruleDTO
     * @return
     */
    public void addFileRule(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            return;
        }

        // 设置主键ID
        ruleDTO.setId(ComUtil.getId());

        // 设置权重默认值
        setDefaultWeight(ruleDTO);

        // 规则持久化
        this.insert(ruleDTO);

        // 审计日志
        BdpLogUtil.log4Insert(ruleDTO);
    }

    /**
     * 更新规则，支持单表校验、跨表校验
     *
     * @param ruleDTO
     * @return
     */
    public void updateStructuredDataRule(RuleDTO ruleDTO) {
        checkStructuredDataUpdateValid(ruleDTO);

        updateStructuredData2Db(ruleDTO);
    }

    /**
     * 更新跨库校验规则
     *
     * @param ruleDTO
     * @return
     */
    public void updateMultiDbRule(RuleDTO ruleDTO) {
        checkMultiDbUpdateValid(ruleDTO);

        updateMultiDb2Db(ruleDTO);
    }

    /**
     * 持久化DB
     *
     * @param ruleDTO
     */
    private void saveSturcturedData2Db(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            return;
        }

        // 自定义SQL规则需要设置数据源列表
        setDefaultDatasourceList(ruleDTO);

        // 设置主键ID
        ruleDTO.setId(ComUtil.getId());

        // 设置异常SQL、表行数统计SQL
        setRuleSql(ruleDTO);

        // 设置权重默认值
        setDefaultWeight(ruleDTO);

        // 规则持久化
        this.insert(ruleDTO);

        // 规则数据源配置持久化
        saveDs2Db(ruleDTO);

        // 规则告警配置持久化
        this.alarmService.batchInsertRuleAlarm(ruleDTO.getId(), ruleDTO.getTemplateId(), Arrays.asList(ruleDTO.getAlarmConfig()));

        // 模板参数持久化
        this.ruleVariableService.addVariable(ruleDTO);

        // 审计日志
        BdpLogUtil.log4Insert(ruleDTO);
    }

    /**
     * 持久化DB
     *
     * @param ruleDTO
     */
    private void saveMultiDb2Db(RuleDTO ruleDTO) {
        // TODO:待实现
    }

    /**
     * 持久化DB
     *
     * @param ruleDTO
     */
    private void updateStructuredData2Db(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            return;
        }

        RuleDTO oldDto = this.selectByPrimaryKey(ruleDTO.getId());
        if (oldDto == null) {
            throw new BusinessException("根据ID未找到作业，id=" + ruleDTO.getId() + "，更新失败");
        }

        // 自定义SQL规则需要设置数据源列表
        setDefaultDatasourceList(ruleDTO);

        // 生成异常SQL、表行数SQL
        setRuleSql(ruleDTO);

        // 规则持久化
        ruleDTO.setCreatedBy(ThreadContextHelper.getUserId());
        this.updateByPrimaryKeySelective(ruleDTO);

        // 规则数据源配置持久化
        updateDs2Db(ruleDTO);

        // 规则告警配置持久化
        this.alarmService.batchUpdateRuleAlarm(ruleDTO.getId(), ruleDTO.getTemplateId(), Arrays.asList(ruleDTO.getAlarmConfig()));

        // 模板参数持久化
        this.ruleVariableService.updateVariable(ruleDTO);

        // 审计日志
        BdpLogUtil.log4Update(ruleDTO, oldDto);
    }

    /**
     * 持久化DB
     *
     * @param ruleDTO
     */
    private void updateMultiDb2Db(RuleDTO ruleDTO) {
        // TODO:待实现
    }

    /**
     * 持久化DB
     *
     * @param ruleDTO
     */
    private void updateFileRule2Db(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            return;
        }

        RuleDTO oldDto = this.selectByPrimaryKey(ruleDTO.getId());
        if (oldDto == null) {
            throw new BusinessException("根据ID未找到作业，id=" + ruleDTO.getId() + "，更新失败");
        }

        // 规则持久化
        ruleDTO.setCreatedBy(ThreadContextHelper.getUserId());
        this.updateByPrimaryKeySelective(ruleDTO);

        // 规则数据源配置持久化
        updateDs2Db(ruleDTO);

        // 规则告警配置持久化
        this.alarmService.batchUpdateRuleAlarm(ruleDTO.getId(), ruleDTO.getTemplateId(), Arrays.asList(ruleDTO.getAlarmConfig()));

        // 模板参数持久化
        this.ruleVariableService.updateVariable(ruleDTO);

        // 审计日志
        BdpLogUtil.log4Update(ruleDTO, oldDto);
    }

    /**
     * 设置默认数据源列表
     * 注意：
     * 1、自定义SQL，需要从SQL解析出tableName、columnName；
     * 2、iotdb特殊处理，无法使用Druid解析SQL语句，不需要保存数据源信息；
     *
     * @param ruleDTO
     */
    private void setDefaultDatasourceList(RuleDTO ruleDTO) {
        if (ruleDTO.getTemplateType() == null) {
            logger.debug("规则模板类型为空，不需要设置数据源列表");
            return;
        }
        if (ruleDTO.getTemplateType() != TemplateType.CUSTOM_SQL.getCode()) {
            logger.debug("仅自定义SQL规则需要设置数据源列表,templateType=" + ruleDTO.getTemplateType());
            return;
        }
        if (StringUtils.isEmpty(ruleDTO.getAbnormalSql())) {
            logger.error("SQL为空，自定义SQL规则设置数据源列表失败");
            return;
        }

        // 获取作业信息
        JobDTO jobDetail = this.jobDAO.getJobDetail(ruleDTO.getJobId());
        if (jobDetail == null) {
            logger.error("根据ID[{}]未找到查询作业，自定义SQL规则设置数据源列表失败", ruleDTO.getJobId());
            return;
        }

        // 获取数据源信息;进一步获取数据源类型；然后根据数据源类型获取DbType，再做进一步的语法检查
        DbType dbType = this.getDbType(jobDetail.getDatasourceId());
        if (dbType == null) {
            logger.error("根据ID[{}]未找到数据源类型，自定义SQL规则设置数据源列表失败", jobDetail.getDatasourceId());
            return;
        }

        // 获取表名称
        List<String> talbeNameList = getTableFromSql(dbType, ruleDTO.getAbnormalSql());
        if (CollectionUtils.isEmpty(talbeNameList)) {
            throw new BusinessException("仅支持DQL语句。DQL语句中必须包含表名称，不支持类似select 1 as value之类的语句。");
        }

        // 单表校验需要获取字段名称
        List<String> columnNameList = null;
        if (ruleDTO.getMeasureType() == MeasureType.SINGLE_SOURCE.getCode()) {
            columnNameList = getColumnFromSql(dbType, ruleDTO.getAbnormalSql());
        }

        // 设置数据源信息
        setDataSourceList(ruleDTO, jobDetail, talbeNameList, columnNameList);
    }

    /**
     * 获取数据源类型
     *
     * @param datasourceId
     * @return
     */
    private DbType getDbType(String datasourceId) {
        String datasourceType = this.qaDsService.getDbType(datasourceId);
        if (StringUtils.isEmpty(datasourceType)) {
            logger.error("根据ID[{}]未找到数据源，自定义SQL规则设置数据源列表失败", datasourceId);
            return null;
        }

        if (datasourceType.equals("iotdb")) {
            return null;
        }

        DbType dbType = null;
        try {
            dbType = DbType.valueOf(datasourceType);
        } catch (Exception e) {
            logger.error("获取数据源类型失败,errMsg={}", e);
        }

        return dbType;
    }

    /**
     * 从SQL中获取表名称
     *
     * @param dbType
     * @param sql
     * @return
     */
    private List<String> getTableFromSql(DbType dbType, String sql) {
        if (StringUtils.isEmpty(sql)) {
            return new ArrayList<>();
        }

        List<String> tableList = new ArrayList<>();
        try {
            List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
            for (SQLStatement stmt : stmtList) {
                // 仅支持DQL语句
                if (!isDql(stmt)) {
                    continue;
                }

                SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(dbType);
                stmt.accept(visitor);
                Map<TableStat.Name, TableStat> tableMap = visitor.getTables();
                if (tableMap != null && tableMap.size() > 0) {
                    for (TableStat.Name item : tableMap.keySet()) {
                        if (item != null) {
                            tableList.add(item.getName());
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("SQL校验失败，errMsg=", e);
            throw new BusinessException("SQL校验失败", e);
        }

        return tableList;
    }

    /**
     * 从SQL中获取字段名称
     *
     * @param dbType
     * @param sql
     * @return
     */
    private List<String> getColumnFromSql(DbType dbType, String sql) {
        if (StringUtils.isEmpty(sql)) {
            return new ArrayList<>();
        }

        List<String> columnList = new ArrayList<>();
        try {
            List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
            for (SQLStatement stmt : stmtList) {
                // 仅支持DQL语句
                if (!isDql(stmt)) {
                    continue;
                }

                SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(dbType);
                stmt.accept(visitor);
                Collection<TableStat.Column> columnCollection = visitor.getColumns();
                for (TableStat.Column col : columnCollection) {
                    // 针对*或1特殊处理，不填充字段名称
                    if (col.getName().equals("*") || col.getName().equals("1")) {
                        continue;
                    }
                    columnList.add(col.getName());
                }
            }
        } catch (Exception e) {
            logger.error("SQL校验失败，errMsg=", e);
            throw new BusinessException("SQL校验失败", e);
        }

        return columnList;
    }

    /**
     * 判断是否为DQL语句（仅支持DQL语句）
     *
     * @param stmt
     * @return true：是DQL语句；false：不是DQL语句；
     */
    private boolean isDql(SQLStatement stmt) {
        if (stmt instanceof SQLSelectStatement) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 设置数据源信息
     *
     * @param jobDetail
     * @param talbeNameList
     * @param columnNameList
     * @return
     */
    private void setDataSourceList(RuleDTO ruleDTO, JobDTO jobDetail, List<String> talbeNameList, List<String> columnNameList) {
        List<QaDataSourceDTO> datasourceList = new ArrayList<>();
        for (String tableName : talbeNameList) {
            QaDataSourceDTO ds = new QaDataSourceDTO();
            ds.setDatasourceId(jobDetail.getDatasourceId());
            ds.setDatasourceName(jobDetail.getDatasourceName());
            ds.setDbName(jobDetail.getDbName());
            ds.setTableName(tableName);
            ds.setField(CollectionUtils.isEmpty(columnNameList) ? null : String.join(",", columnNameList));

            datasourceList.add(ds);
        }

        ruleDTO.setDataSourceList(datasourceList);
    }

    /**
     * 设置异常SQL、表行数统计SQL
     *
     * @param ruleDTO
     */
    private void setRuleSql(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            throw new BusinessException("规则为空");
        }
        if (CollectionUtils.isEmpty(ruleDTO.getDataSourceList())) {
            logger.debug("规则[{}]未配置数据源信息");
            return;
        }

        MeasureType measureType = MeasureType.getTypeByCode(ruleDTO.getMeasureType());
        switch (measureType) {
            case SINGLE_SOURCE:
                this.sqlTemplateConverter.covertSingleSrcSQL(ruleDTO);
                break;
            case MULTI_SOURCE:
                this.sqlTemplateConverter.covertMultiSrcSQL(ruleDTO);
                break;
            case MULTI_DATABASE:
                this.sqlTemplateConverter.covertMultiDbSQL(ruleDTO);
                break;
            default:
                throw new BusinessException("暂不支持" + measureType.getDesc());
        }
    }

    /**
     * 设置默认权重
     *
     * @param ruleDTO
     */
    private void setDefaultWeight(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            throw new BusinessException("规则为空");
        }

        ruleDTO.setWeight(ruleDTO.getWeight() == null ? QaConstant.DEFAULT_WEIGHT : ruleDTO.getWeight());
    }

    /**
     * 持久化规则关联数据源到DB
     *
     * @param ruleDTO
     */
    private void saveDs2Db(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            return;
        }

        if (CollectionUtils.isEmpty(ruleDTO.getDataSourceList())) {
            return;
        }

        List<QaDataSourceDTO> dsList = new ArrayList<>();
        for (QaDataSourceDTO qaDs : ruleDTO.getDataSourceList()) {
            DataSourceDTO dataSource = this.commDsService.queryDataSourceById(qaDs.getDatasourceId());
            if (dataSource == null) {
                throw new BusinessException("未找到指定数据源,创建或更新质量规则失败,jobId=" + ruleDTO.getId());
            }

            qaDs.setId(ComUtil.getId());
            qaDs.setBusinessId(ruleDTO.getId());
            qaDs.setDatasourceType(dataSource.getDatasourceType());
            qaDs.setDatasourceName(dataSource.getDatasourceName());
            dsList.add(qaDs);
        }

        this.qaDsService.batchInsertDs(ruleDTO.getId(), dsList);
    }

    /**
     * 持久化规则关联数据源到DB
     *
     * @param ruleDTO
     */
    private void updateDs2Db(RuleDTO ruleDTO) {
        if (ruleDTO == null) {
            return;
        }

        if (CollectionUtils.isEmpty(ruleDTO.getDataSourceList())) {
            return;
        }

        List<QaDataSourceDTO> dsList = new ArrayList<>();
        for (QaDataSourceDTO qaDs : ruleDTO.getDataSourceList()) {
            DataSourceDTO dataSource = this.commDsService.queryDataSourceById(qaDs.getDatasourceId());
            if (dataSource == null) {
                throw new BusinessException("未找到指定数据源,创建或更新质量规则失败,jobId=" + ruleDTO.getId());
            }

            qaDs.setId(ComUtil.getId());
            qaDs.setBusinessId(ruleDTO.getId());
            qaDs.setDatasourceType(dataSource.getDatasourceType());
            qaDs.setDatasourceName(dataSource.getDatasourceName());
            dsList.add(qaDs);

            PojoUtil.setSysProperties(qaDs, PlatformConstant.OpType.insert);
        }

        this.qaDsService.batchUpdateDs(ruleDTO.getId(), dsList);
    }

    /**
     * 通过平台API将字段值转换为名称，包括通用选择组件、通用代码
     *
     * @param dtoList
     */
    private void valueConvert(List<RuleDTO> dtoList) {
        if (CollectionUtils.isEmpty(dtoList)) {
            return;
        }

        // 设置创建者名称
        Map<String, Set<String>> convertFormData = new HashMap<>();
        for (RuleDTO dto : dtoList) {
            BusinessUtil.createConvertSet(convertFormData, SystemConstant.USER, dto.getCreatedBy());
        }
        if (convertFormData.size() > 0) {
            //获取请求结果
            Map<String, Map<String, String>> convertResultData = convertColumnClient.replace(convertFormData);
            //循环设置Alias或Name的值
            for (RuleDTO dto : dtoList) {
                dto.setCreatedBy(BusinessUtil.convertFormat(convertResultData, SystemConstant.USER, dto.getCreatedBy()));
            }
        }

        // 设置枚举类型、质量检查结果分析关联属性
        int totalSum = 0;
        for (RuleDTO dto : dtoList) {
            // 设置规则关联枚举值
            setRuleCommEnum(dto);

            // 设置规则校验配置
            setRuleAlarm(dto);

            // 设置规则参数
            setRuleVariable(dto);

            // 设置规则计算相关属性
            setRuleCalcAttrs(dto);

            // 权重信息
            if (dto.getWeight() != null) {
                totalSum += dto.getWeight();
            }
        }

        // 计算权重占比，保留4位有效数字、四舍五入、保证总和为1
        setRuleWeightRatio(dtoList, totalSum);
    }

    /**
     * 设置规则关联枚举值
     *
     * @param dto
     */
    private void setRuleCommEnum(RuleDTO dto) {
        if (dto == null) {
            return;
        }

        dto.setMeasureTypeName(MeasureType.getDescByCode(dto.getMeasureType()));
        dto.setBlockingTypeName(BlockingType.getDescByCode(dto.getBlockingType()));
        dto.setTemplateTypeName(TemplateType.getDescByCode(dto.getTemplateType()));
    }

    /**
     * 设置规则校验配置
     *
     * @param dto
     */
    private void setRuleAlarm(RuleDTO dto) {
        if (dto == null) {
            return;
        }

        List<AlarmConfigDTO> alarmList = this.alarmService.getAlarmList(dto.getId());
        if (CollectionUtils.isNotEmpty(alarmList)) {
            dto.setAlarmConfig(alarmList.get(0));
        }
    }

    /**
     * 设置规则变量
     *
     * @param dto
     */
    private void setRuleVariable(RuleDTO dto) {
        if (dto == null) {
            return;
        }

        RuleVariableDTO ruleVariable = this.ruleVariableService.getVariableByRuleId(dto.getId());
        if (ruleVariable != null) {
            dto.setVariables(ruleVariable.getArguementValue());
            dto.setArguementList(JSON.parseArray(ruleVariable.getArguementValue(), RuleTemplateArguementDTO.class));
        }
    }

    /**
     * 设置规则计算相关属性
     *
     * @param dto
     */
    private void setRuleCalcAttrs(RuleDTO dto) {
        if (dto == null) {
            return;
        }

        RuleTemplateDTO ruleTemplate = this.ruleTemplateService.getRuleTemplateDetail(dto.getTemplateId());
        dto.setCalcAttribute(ruleTemplate);
    }

    /**
     * 设置权重占比
     *
     * @param dtoList
     * @param totalSum
     */
    private void setRuleWeightRatio(List<RuleDTO> dtoList, int totalSum) {
        if (totalSum > 0) {
            int cntWeight = 0;
            double cntRatio = 0.0;
            for (RuleDTO dto : dtoList) {
                if (dto.getWeight() != null) {
                    if (totalSum - cntWeight == dto.getWeight()) {
                        BigDecimal totalWeight = new BigDecimal(1 - cntRatio);
                        System.out.println(1 - cntRatio);
                        dto.setWeightRatio(totalWeight.setScale(QaConstant.DEFAULT_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue());
                        break;
                    }

                    BigDecimal weight = new BigDecimal(dto.getWeight());
                    BigDecimal totalWeight = new BigDecimal(totalSum);
                    dto.setWeightRatio(weight.divide(totalWeight, QaConstant.DEFAULT_SCALE, BigDecimal.ROUND_HALF_UP).doubleValue());

                    cntRatio += dto.getWeightRatio();
                    cntWeight += dto.getWeight();
                }
            }
        }
    }

    /**
     * 获取数据源ID
     *
     * @param jobId
     * @return
     */
    private String getDsIdFromJob(String jobId) {
        if (StringUtils.isEmpty(jobId)) {
            return null;
        }

        JobDTO jobDetail = this.jobDAO.getJobDetail(jobId);
        if (jobDetail == null) {
            logger.error("根据ID[{}]未找到查询作业，自定义SQL规则设置数据源列表失败", jobId);
            return null;
        }

        return jobDetail.getDatasourceId();
    }

    /**
     * 设置文件列表
     *
     * @param dto
     */
    private void setFileList(RuleDTO dto) {
        if (dto.getMeasureType() != MeasureType.FILE.getCode()) {
            return;
        }
        if (CollectionUtils.isEmpty(dto.getDataSourceList())) {
            return;
        }

        Set<String> fileIdSet = new HashSet<>();
        for (QaDataSourceDTO ds : dto.getDataSourceList()) {
            if (ds == null) {
                continue;
            }
            if (StringUtils.isEmpty(ds.getWhiteList())) {
                continue;
            }

            fileIdSet.addAll(Arrays.asList(ds.getWhiteList().trim().split(QaConstant.COMMA)));
        }

        if (CollectionUtils.isEmpty(fileIdSet)) {
            dto.setFileList(new ArrayList<>());
        } else {
            dto.setFileList(this.qaDsService.getFileList(null, new ArrayList<>(fileIdSet)));
        }
    }

    /**
     * 是否为结构化数据校验
     *
     * @param type
     * @return
     */
    private boolean isStructDataMeasure(Integer type) {
        if (type != null) {
            return type == MeasureType.SINGLE_SOURCE.getCode() || type == MeasureType.MULTI_SOURCE.getCode();
        }

        return false;
    }

    /**
     * 获取待校验文件
     *
     * @param jobDTO
     * @return
     */
    private List<FtdpFileDTO> getFileList(JobDTO jobDTO) {
        // 工程值文件校验
        if (FileType.PROCESS_DATA.getCode() == jobDTO.getFileType()) {
            if (StringUtils.isNotEmpty(jobDTO.getResourceIds())) {
                return this.qaDsService.getFileList(null, Arrays.asList(jobDTO.getResourceIds().trim().split(QaConstant.COMMA)));
            }

            return new ArrayList<>();
        }

        // 原始文件校验
        return this.qaDsService.getFileList(jobDTO.getOffsiteDataId(), jobDTO.getFileIdList());
    }
}
